// Copyright (c) Edgeless Systems GmbH.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package cmd

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"time"

	"github.com/edgelesssys/marblerun/util"
)

// certificateLegacy acts as a handler for generating signed certificates.
type certificateLegacy struct {
	namespace     string
	serverPrivKey *rsa.PrivateKey
	serverCert    *pem.Block
	caPrivKey     *rsa.PrivateKey
	caCert        *pem.Block
}

// newCertificateLegacy creates a certificate handler for kubernetes versions <=18.
func newCertificateLegacy(namespace string) (*certificateLegacy, error) {
	serial, err := util.GenerateCertificateSerialNumber()
	if err != nil {
		return nil, err
	}
	crt := &certificateLegacy{namespace: namespace}

	caCert := &x509.Certificate{
		SerialNumber: serial,
		Subject: pkix.Name{
			Organization: []string{"edgeless.systems"},
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(1, 0, 0),
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
	}

	caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		return nil, err
	}
	crt.caPrivKey = caPrivKey

	caPub := &caPrivKey.PublicKey
	caBytes, err := x509.CreateCertificate(rand.Reader, caCert, caCert, caPub, caPrivKey)
	if err != nil {
		return nil, err
	}

	crt.caCert = &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: caBytes,
	}

	serverPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		return nil, fmt.Errorf("failed creating rsa private key: %w", err)
	}
	crt.serverPrivKey = serverPrivKey

	return crt, nil
}

// get returns the signed certificate of the webhook server.
func (crt *certificateLegacy) get(_ context.Context) ([]byte, error) {
	certBytes := pem.EncodeToMemory(crt.serverCert)
	return certBytes, nil
}

// setCaBundle sets the CABundle field to the self signed rootCA generated by the handler.
func (crt *certificateLegacy) setCaBundle() ([]string, error) {
	caCertBytes := pem.EncodeToMemory(crt.caCert)
	injectorValues := []string{
		fmt.Sprintf("marbleInjector.start=%t", true),
		fmt.Sprintf("marbleInjector.CABundle=%s", base64.StdEncoding.EncodeToString(caCertBytes)),
	}
	return injectorValues, nil
}

// signRequest signs the webhook certificate using the rootCA.
func (crt *certificateLegacy) signRequest(_ context.Context) error {
	serial, err := util.GenerateCertificateSerialNumber()
	if err != nil {
		return err
	}
	serverCert := &x509.Certificate{
		SerialNumber: serial,
		Subject: pkix.Name{
			CommonName:   fmt.Sprintf("system:node:%s.svc", webhookDNSName(crt.namespace)),
			Organization: []string{"system:nodes"},
		},
		DNSNames:           []string{fmt.Sprintf("%s.svc", webhookDNSName(crt.namespace))},
		SignatureAlgorithm: x509.SHA256WithRSA,
		KeyUsage:           x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:        []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		NotBefore:          time.Now(),
		NotAfter:           time.Now().AddDate(1, 0, 0),
	}

	certData := crt.caCert.Bytes
	caCertx509, err := x509.ParseCertificate(certData)
	if err != nil {
		return err
	}

	serverPub := &crt.serverPrivKey.PublicKey
	serverCertBytes, err := x509.CreateCertificate(rand.Reader, serverCert, caCertx509, serverPub, crt.caPrivKey)
	if err != nil {
		return err
	}

	crt.serverCert = &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: serverCertBytes,
	}

	return nil
}

// getKey returns the private key of the webhook server.
func (crt certificateLegacy) getKey() *rsa.PrivateKey {
	return crt.serverPrivKey
}
